﻿using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace VoxelSystem.VoxelSystem
{
    public struct VertexPositionNormalColored
    {
        public Vector3 Position;
        public Color Color;
        public Vector3 Normal;

        public VertexPositionNormalColored(Vector3 position, Color color)
        {
            Position = position;
            Color = color;
            Normal = new Vector3(0, 1, 0);
        }
        public VertexPositionNormalColored(Vector3 position, Color color, Vector3 normal)
        {
            Normal = normal;
            Position = position;
            Color = color;            
        }

        public static int SizeInBytes = 7 * 4;
        public static VertexElement[] VertexElements = new VertexElement[] 
        {
            new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
            new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0),
            new VertexElement(0, sizeof(float)*4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0)
        };
    }

    public class VS_VoxelSystem
    {
        private TestGame _root;
        private VS_Settings _settings = new VS_Settings();

        private List<VS_Voxel> _voxels;
        private List<VS_GridCube> _grid;
        private int _selectedVoxelIndex = 0;
        public int VoxelCnt = 0;

        private VertexPositionColor[] _gridPointList;
        private VertexPositionColor[] _voxelPointList;
        
        private List<VertexPositionNormalColored> _vertices;
        private List<int> _verticeIndices;

        public VS_VoxelSystem(TestGame root)
        {
            _root = root;

            _grid = new List<VS_GridCube>(); 
            _voxels = new List<VS_Voxel>();
            _vertices = new List<VertexPositionNormalColored>();
            _verticeIndices = new List<int>();

            loadTest();
        }

        public static Vector3 GetSnappedVector3(Vector3 v, float snapSize)
        {
            return new Vector3( snapSize * (float)Math.Floor(v.X / snapSize),
                                snapSize * (float)Math.Floor(v.Y / snapSize),
                                snapSize * (float)Math.Floor(v.Z / snapSize));
        }
        public static Color LerpColor(float value)
        {
            float r = 0.0f;
            float g = 0.0f;
            if (value < 0.5f)
            {
                r = 1;
                g = value * 2;
            }
            else
            {
                g = 1;
                r = 1 - ((value - 0.5f) * 2);
            }
            return new Color(r, g, 0);
        }
        public static int[,] EdgeDefinition = new int[12, 2] {  {0,1}, {3,2}, {7,6}, {4,5},
                                                                {0,3}, {1,2}, {5,6}, {4,7},
                                                                {0,4}, {1,5}, {2,6}, {3,7}}; 

        public void ProcessInput(KeyboardState keyState, float amount)
        {
            float speed = 0.25f;

            Vector3 oldVoxelPosition = CurrentVoxel.Position;
            if (keyState.IsKeyDown(Keys.W))
                oldVoxelPosition.Z -= speed * amount;
            if (keyState.IsKeyDown(Keys.S))
                oldVoxelPosition.Z += speed * amount;
            if (keyState.IsKeyDown(Keys.A))
                oldVoxelPosition.X -= speed * amount;
            if (keyState.IsKeyDown(Keys.D))
                oldVoxelPosition.X += speed * amount;
            if (keyState.IsKeyDown(Keys.R))
                oldVoxelPosition.Y += speed * amount;
            if (keyState.IsKeyDown(Keys.F))
                oldVoxelPosition.Y -= speed * amount;

            // process any changes
            if (oldVoxelPosition != CurrentVoxel.Position)
            {
                CurrentVoxel.Position = oldVoxelPosition;
                _root.CameraTarget = CurrentVoxel.Position;
//                System.Diagnostics.Debug.WriteLine(String.Format("x: {0}\t\ty: {1}\t\tz:{2}", oldVoxelPosition.X, oldVoxelPosition.Y, oldVoxelPosition.Z));
                updateGrid();
            }

            if (keyState.IsKeyDown(Keys.Tab))
            {
                nextVoxel();
                //System.Diagnostics.Debug.WriteLine(_grid[0].CornerWeights[0].ToString());
            }
        }
        public void Update(GameTime gameTime)
        {

        }
        public void Draw(GameTime gameTime)
        {
            // draw grid
            //_root.Device.RenderState.FillMode = FillMode.Point;
            _root.Device.RenderState.PointSize = 3;

            _root.Device.DrawUserPrimitives(PrimitiveType.PointList, _gridPointList, 0, _gridPointList.Length);

            // draw voxel
            _root.Device.RenderState.PointSize = 5;
            _root.Device.DrawUserPrimitives(PrimitiveType.PointList, _voxelPointList, 0, _voxelPointList.Length);

            // draw Verts
            _root.Device.RenderState.FillMode = FillMode.WireFrame;
            //_root.Device.RenderState.CullMode = CullMode.None;
            

            _root.Device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _vertices.ToArray(), 0, _vertices.Count, _verticeIndices.ToArray(), 0, _verticeIndices.Count/3);
            /*VertexPositionColor[] testV = new VertexPositionColor[8];
            testV[0] = new VertexPositionColor(new Vector3(-0.2f, 0, 0), Color.White);
            testV[1] = new VertexPositionColor(new Vector3(-0.2f, 0.2f, 0), Color.Red);
            testV[2] = new VertexPositionColor(new Vector3(0.2f, 0, 0), Color.Green);
            testV[3] = new VertexPositionColor(new Vector3(0.2f, 0.2f, 0), Color.Blue);

            testV[4] = new VertexPositionColor(new Vector3(0.05f, -0.15f, 0), Color.Yellow);
            testV[5] = new VertexPositionColor(new Vector3(-0.05f, -0.15f, 0), Color.Magenta);
            testV[6] = new VertexPositionColor(new Vector3(-0.1f, -0.15f, 0), Color.Yellow);
            testV[7] = new VertexPositionColor(new Vector3(-0.15f, -0.15f, 0), Color.Magenta);

            short[] testI = new short[8];
            testI[0] = 0;
            testI[1] = 1;
            testI[2] = 2;

            testI[3] = 1;
            testI[4] = 3;
            testI[5] = 2;
            _root.Device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, testV, 0, testV.Length, testI, 0, 2);*/
        }

        private void updateGrid()
        {
            _grid = new List<VS_GridCube>();

            _voxelPointList = new VertexPositionColor[_voxels.Count];
            int tmp_cntVoxel = 0;
            foreach (VS_Voxel vox in _voxels)
            {
                addGridCubeByVoxel(vox);
                                
                Color c = Color.Blue;                
                if (tmp_cntVoxel == _selectedVoxelIndex)
                    c = Color.Cyan;

                _voxelPointList[tmp_cntVoxel] = new VertexPositionColor(vox.Position, Color.Blue);
                tmp_cntVoxel++;
            }

            _gridPointList = new VertexPositionColor[_grid.Count * 8];
            int tmp_cntGrid = 0;
            foreach (VS_GridCube gc in _grid)
            {
                gc.UpdateInfluencingVoxels();
                if (!gc.Filled && !gc.Empty)
                {
                    for (int n = 0; n < 8; n++)
                    {
                        _gridPointList[tmp_cntGrid * 8 + n] = new VertexPositionColor(gc.Corners[n], VS_VoxelSystem.LerpColor(gc.CornerWeights[n]));
                    }
                }
                tmp_cntGrid++;
            } 

            /*_gridPointList = new VertexPositionColor[_grid.Count];
            int tmp_cntGrid = 0;
            foreach (VS_GridCube gc in _grid)
            {
                gc.UpdateInfluencingVoxels();
                _gridPointList[tmp_cntGrid] = new VertexPositionColor(gc.CenterPosition, VS_VoxelSystem.LerpColor(gc.CornerWeights[0]));
                tmp_cntGrid++;
            }*/
        }
        private void addGridCubeByVoxel(VS_Voxel byVoxel)
        {
            if (_settings.GridType == GridType.DynamicSnap)
            {
                Vector3 snapPosition = VS_VoxelSystem.GetSnappedVector3(byVoxel.Position + new Vector3(VS_Settings.GridDistance / 2, VS_Settings.GridDistance / 2, VS_Settings.GridDistance/2), VS_Settings.GridDistance);

                int gridCubeIndex = _grid.FindIndex(gc => gc.CenterPosition == snapPosition);
                if (gridCubeIndex >= 0 && gridCubeIndex < _grid.Count)
                {
                    _grid[gridCubeIndex].AddInfluencingVoxel(byVoxel);
                }
                else
                {
                    VS_GridCube newGridCube = new VS_GridCube(this, snapPosition);
                    newGridCube.AddInfluencingVoxel(byVoxel);
                    _grid.Add(newGridCube);
                }

                // Neighbours
                int tmp_neighborDepth = 0;
                int tmp_neighborCnt = 0;
                do
                {
                    tmp_neighborDepth++;
                    tmp_neighborCnt = 0;

                    // pass 1
                    for (float offsetX = -((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetX <= ((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetX += VS_Settings.GridDistance)
                    {
                        for (float offsetY = -((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetY <= ((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetY += VS_Settings.GridDistance)
                        {
                            Vector3 v_xy1 = new Vector3(offsetX, offsetY, -((float)tmp_neighborDepth * VS_Settings.GridDistance));
                            Vector3 v_xy2 = new Vector3(offsetX, offsetY, -v_xy1.Z);
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_xy1));
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_xy2));
                        }
                    }

                    // pass 2
                    for (float offsetY = -((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetY <= ((float)tmp_neighborDepth * VS_Settings.GridDistance);
                            offsetY += VS_Settings.GridDistance)
                    {
                        for (float offsetZ = -((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetZ <= ((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetZ += VS_Settings.GridDistance)
                        {
                            Vector3 v_yz1 = new Vector3(-(float)tmp_neighborDepth * VS_Settings.GridDistance, offsetY, offsetZ);
                            Vector3 v_yz2 = new Vector3(-v_yz1.X, offsetY, offsetZ);
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_yz1));
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_yz2));
                        }
                    }

                    // pass 3
                    for (float offsetX = -((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetX <= ((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetX += VS_Settings.GridDistance)
                    {
                        for (float offsetZ = -((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetZ <= ((float)(tmp_neighborDepth - 1) * VS_Settings.GridDistance);
                            offsetZ += VS_Settings.GridDistance)
                        {
                            Vector3 v_xz1 = new Vector3(offsetX, -(float)tmp_neighborDepth * VS_Settings.GridDistance, offsetZ);
                            Vector3 v_xz2 = new Vector3(offsetX, -v_xz1.Y, offsetZ);
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_xz1));
                            tmp_neighborCnt += addGridCubeIfInRange(byVoxel, new VS_GridCube(this, snapPosition + v_xz2));
                        }
                    }
                } while (tmp_neighborCnt > 0);
            }
        }
        private int addGridCubeIfInRange(VS_Voxel voxel, VS_GridCube cube)
        {
            if (cube.IsVoxelInRange(voxel,RangeType.NearRange))
            {
                cube.AddInfluencingVoxel(voxel);
                if (cube.CenterPosition == Vector3.Zero)
                    System.Diagnostics.Debug.WriteLine("test");
                addGridCubeDefault(cube);
                return 1;
            }
            return 0;
        }
        private void addGridCubeDefault(VS_GridCube cube)
        {
            int gridCubeIndex = _grid.FindIndex(gc => gc.CenterPosition == cube.CenterPosition);
            if (gridCubeIndex >= 0 && gridCubeIndex < _grid.Count)
            {                
                _grid[gridCubeIndex].AddInfluencingVoxel(cube.InfluencingVoxels[0]);
            }
            else
            {
                _grid.Add(cube);
            }
        }
        private void nextVoxel()
        {
            _selectedVoxelIndex++;
            if (_selectedVoxelIndex >= _voxels.Count)
                _selectedVoxelIndex = 0;
            
            _root.CameraTarget = CurrentVoxel.Position;
        }
        private void loadTest()
        {
            _voxels = new List<VS_Voxel>();
            _voxels.Add(new VS_Voxel(this, new Vector3(0, 0, 0)));
            //_voxels.Add(new VS_Voxel(this, new Vector3(0.2f, 0.02f, 0)));
            _voxels.Add(new VS_Voxel(this, new Vector3(0.5f, -1.3f, 0.7f)));

            updateGrid();
        }

        public VS_Settings Settings
        {
            get { return _settings; }
        }
        public List<VS_Voxel> Voxels
        {
            get { return _voxels; }
        }
        public int VoxelCount
        {
            get { return _voxels.Count; }
        }
        public VS_Voxel CurrentVoxel
        {
            get { return _voxels[_selectedVoxelIndex]; }
        }

        public List<int> VerticeIndices
        {
            get { return _verticeIndices; }
            set { _verticeIndices = value; }
        }
        public List<VertexPositionNormalColored> Vertices
        {
            get { return _vertices; }
            set { _vertices = value; }
        }
    }
}
